Skip to content

improve payment error messages#931

Merged
BilalG1 merged 4 commits intodevfrom
payments-improve-error-msgs
Oct 10, 2025
Merged

improve payment error messages#931
BilalG1 merged 4 commits intodevfrom
payments-improve-error-msgs

Conversation

@BilalG1
Copy link
Copy Markdown
Collaborator

@BilalG1 BilalG1 commented Oct 7, 2025

…or non stackable offer

High-level PR Summary

This PR improves error messaging in the payments system by addressing two main issues: (1) providing clearer error messages when an item ID is mistakenly used as a product ID, including distinguishing between non-existent products, server-only products, and cases where an item exists with that ID, and (2) preventing checkout creation for non-stackable products that a customer already owns by adding an early validation check. The changes update the ProductDoesNotExist known error to include contextual information, introduce a new getCustomerPurchaseContext helper function to check existing purchases, and update related test snapshots to reflect the improved error messages.

⏱️ Estimated Review Time: 15-30 minutes

💡 Review Order Suggestion
Order File Path
1 packages/stack-shared/src/known-errors.tsx
2 apps/backend/src/lib/payments.tsx
3 apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts
4 apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts
5 apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--create-purchase-url.test.ts

Need help? Join our Discord

Analyze latest changes


Important

Enhances payment error handling by adding context to error messages and preventing duplicate purchases of non-stackable products.

  • Behavior:
    • Adds early validation in create-purchase-url/route.ts to prevent checkout for non-stackable products already owned by the customer.
    • Updates ensureProductIdOrInlineProduct in payments.tsx to include context in ProductDoesNotExist error.
    • Introduces getCustomerPurchaseContext in payments.tsx to check existing purchases.
  • Errors:
    • Modifies ProductDoesNotExist in known-errors.tsx to include context (null, server_only, item_exists).
  • Tests:
    • Updates test cases in create-purchase-url.test.ts, purchase-session.test.ts, and validate-code.test.ts to reflect new error messages and behavior.
    • Adds tests for blocking repeat purchases of non-stackable products.

This description was created by Ellipsis for 5495b9c. You can customize this summary. It will automatically update as commits are pushed.


Summary by CodeRabbit

  • New Features

    • Blocks creating a purchase URL when a customer already owns a non-stackable product, preventing duplicate checkouts.
  • Bug Fixes

    • Standardized error responses for missing or server-only products with consistent codes, messages, and structured details.
    • Error payloads now include details.context (null, server_only, or item_exists) instead of access_type.
  • Tests

    • Added and updated tests to cover blocking repeat purchases and the new standardized error format (including inline snapshot updates).

@vercel
Copy link
Copy Markdown

vercel Bot commented Oct 7, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Oct 10, 2025 6:50pm
stack-dashboard Ready Ready Preview Comment Oct 10, 2025 6:50pm
stack-demo Ready Ready Preview Comment Oct 10, 2025 6:50pm
stack-docs Ready Ready Preview Comment Oct 10, 2025 6:50pm

@BilalG1 BilalG1 changed the title fix error messages for using item id as offer id, creating checkout f… improve payment error messages Oct 7, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a pre-purchase ownership check to the create-purchase-url route using a new getCustomerPurchaseContext helper. Refactors payments validation to centralize ownership and catalog checks, updates known error semantics for ProductDoesNotExist to use a context field, and adjusts e2e tests to the new structured error responses and a non-stackable duplicate purchase case.

Changes

Cohort / File(s) Summary
API route: create purchase URL
apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts
Imports tenancy-aware Prisma client and getCustomerPurchaseContext; adds a pre-Stripe runtime check that rejects non-stackable duplicate purchases with a StatusError; switches to structured error handling.
Payments library: validation and context
apps/backend/src/lib/payments.tsx
Adds getCustomerPurchaseContext(...) to aggregate one-time purchases and subscriptions and compute alreadyOwnsProduct; refactors validatePurchaseSession to use it; consolidates product existence/server-only errors through KnownErrors.ProductDoesNotExist (new context semantics); improves add-on/catalog validation logic.
Known errors API
packages/stack-shared/src/known-errors.tsx
Changes ProductDoesNotExist constructor signature and details payload from access_type to context (`"item_exists"
E2E tests: create purchase URL
apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts
Updates expected error payloads to the new structured shape; adds test for blocking non-stackable duplicate purchases after a completed test-mode purchase.
E2E tests: legacy/renamed paths
apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/*
Updates legacy tests to assert the new ProductDoesNotExist structured error (details.context) and new messages; consolidates several flows to snapshot-based assertions for non-stackable duplicate cases.
E2E tests: purchase-session / validate-code flows
apps/e2e/tests/backend/endpoints/api/v1/payments/{purchase-session,validate-code,create-purchase-url}.test.ts
Replaces multi-step checks with inline snapshot or direct 400 assertions for non-stackable duplicate purchase scenarios; replaces old string error bodies with structured error objects.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant API as CreatePurchaseURL Route
  participant Pay as payments.getCustomerPurchaseContext
  participant DB as Prisma (tenancy)
  participant Stripe as Stripe API

  Client->>API: POST /payments/purchases/create-purchase-url (product_id, customer)
  API->>Pay: getCustomerPurchaseContext(tenancy, customer, product_id)
  Pay->>DB: Query one-time purchases + subscriptions
  DB-->>Pay: Purchase/subscription data
  Pay-->>API: { existingOneTimePurchases, subscriptions, alreadyOwnsProduct }

  alt product non-stackable AND alreadyOwnsProduct
    API-->>Client: 400 StatusError ("product not stackable for customer")
  else product passes ownership check
    API->>Stripe: Ensure/create customer & checkout session
    Stripe-->>API: session URL
    API-->>Client: 200 { url }
  end

  note right of API: Product existence/server-only errors use\nKnownErrors.ProductDoesNotExist with details.context
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I thump my paw—no double dip today,
One carrot bought, one hop on the way.
Context says "server-only" or "item_exists"—no fray,
Errors tidy, paths pruned where rules hold sway.
I bound off to Stripe, nose twitching, bright and gay. 🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly indicates that payment error messages are being improved, which reflects part of the changeset related to enhanced error messaging but omits the new validation preventing duplicate purchases of non-stackable products.
Description Check ✅ Passed The pull request description includes the required CONTRIBUTING.md guidelines comment and provides a comprehensive high-level summary of the changes, behavior updates, new helper functions, error modifications, and test adjustments, satisfying the repository’s description template.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch payments-improve-error-msgs

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fa068e and 5495b9c.

📒 Files selected for processing (4)
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

In tests, prefer .toMatchInlineSnapshot where possible; refer to snapshot-serializer.ts for snapshot formatting and handling of non-deterministic values

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: restart-dev-and-test
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: docker
  • GitHub Check: build (22.x)
  • GitHub Check: all-good
  • GitHub Check: Security Check
🔇 Additional comments (4)
apps/e2e/tests/backend/endpoints/api/v1/payments/purchase-session.test.ts (1)

812-818: LGTM! Test correctly reflects the early validation behavior.

The test now properly verifies that the create-purchase-url endpoint rejects duplicate non-stackable purchases upfront, before URL generation. This aligns with the PR's objective to add ownership validation earlier in the flow.

apps/e2e/tests/backend/endpoints/api/v1/payments/validate-code.test.ts (1)

125-131: LGTM! Consistent with the early validation pattern.

The test update correctly reflects that non-stackable duplicate purchases are now blocked at URL creation time, eliminating the need for subsequent validation steps.

apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--validate-code.test.ts (1)

125-131: LGTM! Legacy API test updated consistently.

The test for the v1 API endpoint has been updated to match the behavior change in the latest API, ensuring consistent error handling across API versions.

apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--purchase-session.test.ts (1)

796-802: LGTM! Legacy API test maintains parity with current API.

The test update ensures the v1 API endpoint exhibits the same early validation behavior as the latest API, providing consistent error handling across all API versions.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@recurseml recurseml Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by RecurseML

🔍 Review performed on 017b43f..5fa068e

✨ No bugs found, your code is sparkling clean

✅ Files analyzed, no issues (5)

apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts
apps/backend/src/lib/payments.tsx
apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--create-purchase-url.test.ts
apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts
packages/stack-shared/src/known-errors.tsx

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Summary

This PR improves error message handling in the payments system by standardizing error responses and adding better validation for non-stackable products. The changes span across multiple files to enhance the user experience when working with the payments API.

The key improvements include:

  1. Enhanced error messaging for product lookups: The ProductDoesNotExist error now provides contextual information to distinguish between different scenarios - when a product doesn't exist, when an item ID is used instead of a product ID, or when a server-only product is accessed from client-side code.

  2. Structured error responses: Error messages have been migrated from simple strings to structured JSON objects with error codes, details, and context fields. This follows the established KnownErrors pattern used throughout the codebase for consistent, machine-readable error responses.

  3. Non-stackable product validation: A new validation layer prevents customers from creating purchase URLs for non-stackable products they already own. This is implemented by checking the customer's purchase history using a newly extracted utility function getCustomerPurchaseContext.

  4. Code consolidation: Common customer purchase validation logic has been extracted into the reusable getCustomerPurchaseContext function, reducing code duplication and improving maintainability.

These changes integrate well with the existing codebase architecture, following established patterns for error handling and API consistency. The improvements will make the payments API more developer-friendly by providing clearer, more actionable error messages.

Important Files Changed

Changed Files
Filename Score Overview
apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts 3/5 Added validation for non-stackable product ownership but uses generic StatusError instead of KnownError
apps/backend/src/lib/payments.tsx 4/5 Enhanced ProductDoesNotExist error with context and extracted customer purchase validation logic
packages/stack-shared/src/known-errors.tsx 5/5 Improved ProductDoesNotExist error to use context parameter instead of generic accessType
apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts 4/5 Updated test snapshots and added comprehensive non-stackable product ownership validation test
apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--create-purchase-url.test.ts 5/5 Updated error message snapshots to reflect new structured error format

Confidence score: 4/5

  • This PR is generally safe to merge with good improvements to error messaging and user experience
  • Score reflects solid functionality improvements but inconsistent error handling in one file
  • Pay close attention to apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts which uses StatusError instead of KnownError

Additional Comments (1)

  1. apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--create-purchase-url.test.ts, line 19 (link)

    style: Error message inconsistency: this endpoint still returns a plain string error while other payment errors now return structured JSON objects

5 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts (1)

47-59: Do the non‑stackable ownership check before creating Stripe client; consider consistent error shape

  • Move getStripeForAccount below this block to short‑circuit earlier and avoid unnecessary Stripe work.
  • For consistency with other validation errors (e.g., RedirectUrlNotWhitelisted), consider a KnownError for “already owns non‑stackable” rather than a plain StatusError. At minimum, dedupe the exact message with validatePurchaseSession to avoid drift.

As per coding guidelines (consistent API responses)

apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts (1)

323-388: New non‑stackable duplicate purchase test is solid; minor assertion style tweak

Test flow is good and deterministic. Optional: switch the final body assertion to an inline snapshot for consistency with test style.

-  expect(secondResponse.body).toBe("Customer already has purchased this product; this product is not stackable");
+  expect(secondResponse.body).toMatchInlineSnapshot(
+    "\"Customer already has purchased this product; this product is not stackable\""
+  );

As per coding guidelines (prefer .toMatchInlineSnapshot)

apps/backend/src/lib/payments.tsx (1)

460-466: Avoid message drift; centralize or standardize error

Same “already owns non‑stackable” text appears here and in the route. Consider:

  • Extracting a shared constant for the message, or
  • Introducing a KnownError (e.g., NON_STACKABLE_ALREADY_OWNED) to standardize the response shape and headers across endpoints.

As per coding guidelines (consistent API responses)

Also applies to: 468-470

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 017b43f and 5fa068e.

📒 Files selected for processing (5)
  • apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts (2 hunks)
  • apps/backend/src/lib/payments.tsx (4 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--create-purchase-url.test.ts (2 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts (3 hunks)
  • packages/stack-shared/src/known-errors.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
apps/backend/src/app/api/latest/**

📄 CodeRabbit inference engine (AGENTS.md)

apps/backend/src/app/api/latest/**: Organize backend API routes by resource under /api/latest (e.g., auth at /api/latest/auth/, users at /api/latest/users/, teams at /api/latest/teams/, oauth providers at /api/latest/oauth-providers/)
Use the custom route handler system in the backend to ensure consistent API responses

Files:

  • apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--create-purchase-url.test.ts
  • packages/stack-shared/src/known-errors.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts
  • apps/backend/src/lib/payments.tsx
**/*.test.{ts,tsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

In tests, prefer .toMatchInlineSnapshot where possible; refer to snapshot-serializer.ts for snapshot formatting and handling of non-deterministic values

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--create-purchase-url.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts
🧬 Code graph analysis (3)
apps/backend/src/app/api/latest/payments/purchases/create-purchase-url/route.ts (3)
apps/backend/src/prisma-client.tsx (1)
  • getPrismaClientForTenancy (64-66)
apps/backend/src/lib/payments.tsx (1)
  • getCustomerPurchaseContext (351-378)
packages/stack-shared/src/utils/errors.tsx (1)
  • StatusError (152-261)
apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts (2)
apps/e2e/tests/helpers.ts (1)
  • it (11-11)
apps/e2e/tests/backend/backend-helpers.ts (1)
  • niceBackendFetch (107-171)
apps/backend/src/lib/payments.tsx (5)
packages/stack-shared/src/utils/objects.tsx (1)
  • has (550-553)
packages/stack-shared/src/known-errors.tsx (2)
  • KnownErrors (1575-1577)
  • KnownErrors (1579-1701)
apps/backend/src/prisma-client.tsx (1)
  • PrismaClientTransaction (18-18)
apps/backend/src/lib/tenancies.tsx (1)
  • Tenancy (47-47)
packages/stack-shared/src/utils/strings.tsx (1)
  • typedToUppercase (30-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: Security Check
🔇 Additional comments (7)
packages/stack-shared/src/known-errors.tsx (1)

1498-1512: Context-driven ProductDoesNotExist looks correct

Constructor, details payload, and message branching align with tests and new semantics. No issues.

apps/e2e/tests/backend/endpoints/api/v1/payments/before-offer-to-product-rename/outdated--create-purchase-url.test.ts (2)

58-73: Snapshot updates to structured error payload LGTM

Matches new ProductDoesNotExist shape with details.context = null.


236-249: Server-only case snapshot LGTM

Uses ProductDoesNotExist with details.context = "server_only" as intended.

apps/e2e/tests/backend/endpoints/api/v1/payments/create-purchase-url.test.ts (2)

58-73: Structured error snapshot for non-existent product LGTM

Correct code, message, and details.context = null.


236-249: Server-only structured error snapshot LGTM

Correctly asserts code, headers, and details.context = "server_only".

apps/backend/src/lib/payments.tsx (2)

35-40: Improved diagnostics for wrong product_id LGTM

Using has(...) to detect item_id collisions and returning ProductDoesNotExist with context "item_exists"/"server_only" matches the new semantics and tests.


351-378: getCustomerPurchaseContext helper LGTM

Concise aggregation of purchases/subscriptions and a simple alreadyOwnsProduct flag. Fits reuse in route and validation.

@BilalG1 BilalG1 requested a review from N2D4 October 7, 2025 21:51
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Oct 7, 2025
@github-actions github-actions Bot assigned BilalG1 and unassigned N2D4 Oct 10, 2025
@BilalG1 BilalG1 enabled auto-merge (squash) October 10, 2025 18:44
@BilalG1 BilalG1 merged commit d37b7ea into dev Oct 10, 2025
19 of 20 checks passed
@BilalG1 BilalG1 deleted the payments-improve-error-msgs branch October 10, 2025 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants